#include <math.h>
#include <QtCore/QDebug>
#include <QtGui/QFileDialog>
#include <QtGui/QGraphicsEllipseItem>
#include <QtGui/QGraphicsPolygonItem>
#include <QtGui/QMouseEvent>
#include <QtGui/QPen>
#include <QtGui/QPushButton>
#include <QtGui/QShortcut>
#include <QtGui/QVBoxLayout>
#include "QyGeoEdit.h"

/**************************************************************************************************/

inline QRectF scaleRect( const QRectF & rect, qreal scale ) {

    return QRectF( rect.x() * scale, rect.y() * scale, rect.width() * scale, rect.height() * scale );
}

/**************************************************************************************************/

class QySceneItemEditItem : public QGraphicsEllipseItem {
public:
    QySceneItemEditItem( QyMath::Geometry * geo, qreal size, QGraphicsEllipseItem * ellipseItem = 0, bool centerEditor = false )
        : p_eitem(ellipseItem), p_pitem(0), p_geo(geo), p_index(-1), p_centerEditor(centerEditor)
    {

        init(size);
        QPointF center = p_eitem->pos();

        if (p_centerEditor) {

            setPos(center);

            if ( QGraphicsItem * parent = p_eitem->parentItem() ) {

                setParentItem(parent);
                setBrush( QBrush(Qt::green) );

            } else {

                setBrush( QBrush(Qt::blue) );

            }

        } else {

            center = QPointF( 0, -p_geo->radius() );
            setPos(center);
            setBrush( QBrush(Qt::red) );
            setParentItem(p_eitem);

        }


    }
    QySceneItemEditItem( QyMath::Geometry * geo, qreal size, QGraphicsPolygonItem * polygonItem = 0, int index = -1 )
        : p_eitem(0), p_pitem(polygonItem), p_geo(geo), p_index(index)
    {

        p_centerEditor = ( index < 0 );
        init(size);
        QPointF center = p_pitem->pos();


        if (p_centerEditor) {

            setPos(center);
//             qDebug() << "pos" << center;

            if ( QGraphicsItem * parent = p_pitem->parentItem() ) {

                setParentItem(parent);
                setBrush( QBrush(Qt::green) );

            } else {

                setBrush( QBrush(Qt::blue) );

            }

        } else {

            center = p_pitem->polygon()[p_index];
//             qDebug() << "center" << center;
            setPos(center);
            setBrush( QBrush(Qt::red) );
            setParentItem(p_pitem);

        }

    }

    void init( qreal size ) {

//         qreal halfSize = size / 2.0;
        setRect( QRectF( -3, -3, 6, 6 ) );
        setPen( QPen(Qt::black) );
        setZValue( p_centerEditor ? 10 : 15 );
        setFlags(QGraphicsItem::ItemIsMovable);

    }
    qreal normalizeValue( qreal value ) { return value / 30.0; }

private:
    QVariant itemChange( GraphicsItemChange change, const QVariant & value ) {

        QVariant result = QGraphicsItem::itemChange( change, value );

        if ( change == ItemPositionHasChanged ) {

            if (p_eitem) {

                if (p_centerEditor) {

                    p_geo->center = pos();
                    p_eitem->setPos(p_geo->center);
                    setToolTip( QString("%1, %2")
                        .arg( normalizeValue( p_geo->center.x() ) )
                        .arg( normalizeValue( p_geo->center.y() ) )
                    );

                } else {

                    qreal radius = sqrt( x() * x() + y() * y() );

                    setToolTip( QString("%1").arg( normalizeValue(radius) ) );
                    p_geo->setRadius(radius);
                    QPointF offset( radius, radius );

                    p_eitem->setRect( QRectF( -offset, offset ) );

                }

            }
            if (p_pitem) {

                if (p_centerEditor) {

                    p_geo->center = pos();
                    p_pitem->setPos(p_geo->center);
                    setToolTip( QString("%1, %2")
                        .arg( normalizeValue( p_geo->center.x() ) )
                        .arg( normalizeValue( p_geo->center.y() ) )
                    );

                } else {

                    QPointF center = pos();
                    QPolygonF polygon = p_pitem->polygon();

                    if ( p_index >= 0 && p_index < polygon.count() ) {

                        (*p_geo)[p_index] = center;
                        polygon[p_index] = center;
                        p_pitem->setPolygon(polygon);
                        setToolTip( QString("%1, %2")
                            .arg( normalizeValue( center.x() ) )
                            .arg( normalizeValue( center.y() ) )
                        );

                    }

                }

            }

        }

        return result;
    }
    void contextMenuEvent ( QGraphicsSceneContextMenuEvent * event ) {

//         QMenu menu;

//         QAction * rmItemAction = menu.addAction();

//         QAction * execAction = menu.exec();

    }

    QGraphicsEllipseItem * p_eitem;
    QGraphicsPolygonItem * p_pitem;
    QyMath::Geometry * p_geo;
    int p_index;
    bool p_centerEditor;

};

/**************************************************************************************************/

QyGeoEdit::QyGeoEdit( QWidget * parent ) : QGraphicsView(parent),
    p_mode(DefaultMode),
    p_newCircle(0),
    p_newPolygon(0),
    p_mousePressed(false),
    p_editPanel(0),
    scale(1),
    editItemSize(5)
{

    scene.setSceneRect( -250, -250, 500, 500 );
    setScene(&scene);
    clear();
//     QGraphicsView::scale( 100, 100 );

    substrateImage = scene.addPixmap( QPixmap() );
//     QPixmap bgImage( 10, 10 );

//     QPainter painter(&bgImage);
//     painter.setPen( QColor("#aaaaaa") );
//     painter.setBrush( QBrush("#ffffff") );
//     painter.drawRect( 0, 0, 9, 9 );

//     scene.setBackgroundBrush(bgImage);

    setMouseTracking(true);

}

QyGeoEdit::~QyGeoEdit() {
}

QPointF QyGeoEdit::adjustPos( const QPointF & pos ) {

	return pos;
}

void QyGeoEdit::setGeo( const QyMath::Geometry & geo ) {

    clear();
    p_geo = geo;

    rootItem = addGeoToScene(p_geo);

    for ( int i = 0; i < p_geo.subGeometry.count(); ++i )
        addGeoToScene(p_geo.subGeometry[i]);

}

void QyGeoEdit::addGeo( const QyMath::Geometry & geo ) {

    if (rootItem) {

        p_geo.subGeometry.append(geo);
        addGeoToScene( p_geo.subGeometry.last() );

    } else {

        setGeo(geo);

    }

}

void QyGeoEdit::initDefaultMode() {

    setEditMode(DefaultMode);

}

void QyGeoEdit::initDrawCircleMode() {

    setEditMode(DrawCircleMode);

}

void QyGeoEdit::initDrawPolygonMode() {

    setEditMode(DrawPolygonMode);

}

void QyGeoEdit::setEditMode( QyGeoEdit::EditMode editMode ) {

    p_mode = editMode;
	p_mousePressed = false;
	lastPolygonPoint = 500;

    if (p_newCircle)
        delete p_newCircle;
    p_newCircle = 0;

    if (p_newPolygon)
        delete p_newPolygon;
    p_newPolygon = 0;

}

void QyGeoEdit::showEditPanel() {

    if (!p_editPanel) {

        p_editPanel = new QWidget;
        QVBoxLayout * lo = new QVBoxLayout(p_editPanel);
        QPushButton * defButton = new QPushButton( "Default", p_editPanel ),
            * circleButton = new QPushButton( "Circle", p_editPanel ),
            * polygonButton = new QPushButton( "Polygon", p_editPanel ),
            * clearButton = new QPushButton( "Clear", p_editPanel );

        lo->addWidget(defButton);
        lo->addWidget(circleButton);
        lo->addWidget(polygonButton);
        lo->addWidget(clearButton);

        connect( defButton, SIGNAL( clicked() ), SLOT( initDefaultMode() ) );
        connect( circleButton, SIGNAL( clicked() ), SLOT( initDrawCircleMode() ) );
        connect( polygonButton, SIGNAL( clicked() ), SLOT( initDrawPolygonMode() ) );
        connect( clearButton, SIGNAL( clicked() ), SLOT( clear() ) );

//         scene.addWidget(p_editPanel);

    }

    p_editPanel->show();

}

void QyGeoEdit::clear() {

    scene.clear();
    scene.addLine( 0, -250, 0, 250, QPen(Qt::black) )->setPos( QPointF( 0, 0 ) );
    scene.addLine( -250, 0, 250, 0, QPen(Qt::black) )->setPos( QPointF( 0, 0 ) );
    rootItem = 0;
    p_geo = QyMath::Geometry();

}

bool QyGeoEdit::openSubstrateImage( const QString & filePath ) {

    if ( filePath.isEmpty() ) {

        QString fn = QFileDialog::getOpenFileName();

        return fn.isEmpty() ? false : openSubstrateImage(fn);
    }

    substrateImage->setPixmap( QPixmap(filePath) );

    return true;
}

void QyGeoEdit::zoomIn() {

    scale *= 1.2;

//     foreach ( QGraphicsItem * item, items() ) {

//         if ( QySceneItemEditItem * editItem = dynamic_cast<QySceneItemEditItem*>(item) ) {

//             editItem->setRect( scaleRect( editItem->rect(), 1/scale ) );

//         }

//     }

//     QGraphicsView::scale( scale, scale );
    QGraphicsView::scale( 1.2, 1.2 );

}

void QyGeoEdit::zoomOut() {

    scale /= 1.2;

//     foreach ( QGraphicsItem * item, items() ) {

//         if ( QySceneItemEditItem * editItem = dynamic_cast<QySceneItemEditItem*>(item) ) {

//             editItem->setRect( scaleRect( editItem->rect(), 1/scale ) );

//         }

//     }

    QGraphicsView::scale( 0.8, 0.8 );

}

QAbstractGraphicsShapeItem * QyGeoEdit::addGeoToScene( QyMath::Geometry & geo ) {

    QAbstractGraphicsShapeItem * item = 0;

    if ( geo.isCircle() ) {

        qreal radius = geo.radius();

        if ( radius <= 0 )
            return 0;

        QPointF offset( radius, radius );
        QGraphicsEllipseItem * eitem = new QGraphicsEllipseItem( QRectF( - offset, offset ) );

        eitem->setPos(geo.center);
        scene.addItem(eitem);

        if (rootItem)
            eitem->setParentItem(rootItem);

        scene.addItem( new QySceneItemEditItem( &geo, editItemSize, eitem, true ) );
        scene.addItem( new QySceneItemEditItem( &geo, editItemSize, eitem ) );
        item = eitem;

    } else {

        QGraphicsPolygonItem * pitem = scene.addPolygon(geo);

        if (rootItem)
            pitem->setParentItem(rootItem);

        pitem->setPos(geo.center);
        scene.addItem( new QySceneItemEditItem( &geo, editItemSize, pitem ) );

        for ( int i = 0; i < geo.count(); ++i )
            scene.addItem( new QySceneItemEditItem( &geo, editItemSize, pitem, i ) );

        item = pitem;

    }

    item->setBrush(Qt::NoBrush);
    item->setPen( QPen(Qt::black) );

    return item;
}

void QyGeoEdit::mousePressEvent( QMouseEvent * event ) {

    p_pressPos = mapToScene( event->pos() );

    switch (p_mode) {

    case DrawCircleMode:

        if ( event->buttons() == Qt::LeftButton ) {

            if (p_newCircle)
                delete p_newCircle;

            p_newCircle = scene.addEllipse( QRectF( p_pressPos.x(), p_pressPos.y(), 1, 1 ) );
            p_newCircle->setPos(p_pressPos);
            p_newCircle->setBrush(Qt::NoBrush);
            p_newCircle->setPen( QPen(Qt::black) );

        }

        break;

    case DrawPolygonMode: {

//         if (p_newPolygon)
//             delete p_newPolygon;

        if (p_newPolygon) {

            if ( event->buttons() == Qt::RightButton )
                lastPolygonPoint = 9;
//             QPolygonF polygon = p_newPolygon->polygon();

//             polygon.append(p_pressPos);
//             p_newPolygon->setPolygon(polygon);

        } else {

            QPolygonF geometry;
            geometry.append(p_pressPos);
            lastPolygonPoint = 0;
            p_newPolygon = scene.addPolygon(geometry);
            p_newPolygon->setPos( 0, 0 );
            p_newPolygon->setBrush(Qt::NoBrush);
            p_newPolygon->setPen( QPen(Qt::black) );
        }

        } break;

    default:
        QGraphicsView::mousePressEvent(event);
        break;

    }

    p_mousePressed = true;

}

void QyGeoEdit::mouseMoveEvent( QMouseEvent * event ) {

    QPointF pos = mapToScene( event->pos() );
    QPointF offset = pos - p_pressPos;

    if (p_mousePressed) {

        if (p_newCircle) {

            qreal radius = sqrt( offset.x() * offset.x() + offset.y() * offset.y() );
            QPointF rads( radius, radius );

            p_newCircle->setRect( QRectF( -rads, rads ) );

        }

    }

    if ( p_newPolygon && lastPolygonPoint < 8 ) {

        QPolygonF polygon = p_newPolygon->polygon();

        polygon[lastPolygonPoint] = pos;
        p_newPolygon->setPolygon(polygon);

    }

    QGraphicsView::mouseMoveEvent(event);

}

void QyGeoEdit::mouseReleaseEvent( QMouseEvent * event ) {

    if (p_mousePressed) {

        if (p_newCircle) {

            QyMath::Geometry geo;
            qreal radius = p_newCircle->rect().width() / 2;

            if ( radius > 0 ) {

                geo.setRadius(radius);
                geo.center = calcLocal( p_newCircle->pos() );

                qDebug() << "geo.center" << geo.center;

            }

            p_newCircle->setVisible(false);
            p_newCircle = 0;

            if ( radius > 10 )
                addGeo(geo);

        } else if (p_newPolygon) {

            if ( ++lastPolygonPoint > 7 ) {

                QyMath::Geometry geo;

                geo.center = calcLocal( p_newPolygon->polygon().boundingRect().center() );

//                 qDebug() << "geo.center" << geo.center;

                bool ok = p_newPolygon->polygon().count() > 2;
                if (ok) {

                    foreach( QPointF pos, p_newPolygon->polygon() )
                        geo.append( calcLocal(pos) - geo.center );

                }

                p_newPolygon->setVisible(false);
                p_newPolygon = 0;

                if (ok)
                    addGeo(geo);

            } else {

                QPolygonF polygon = p_newPolygon->polygon();

                polygon.append( event->pos() );
                p_newPolygon->setPolygon(polygon);

            }

        }

    }

    QGraphicsView::mouseReleaseEvent(event);

    p_mousePressed = false;

}

/**************************************************************************************************/
#include <QApplication>

int main( int argCount, char ** args ) {

    QApplication app( argCount, args );
    QyGeoEdit editor;
    /*QyMath::Geometry geo;

    for ( int i = 0; i < 3; ++i ) {

        geo.setRadius(30);
        geo.center = QPointF( 30 * i, 0 );
        editor.addGeo(geo);

    }

    geo.dropCircleFlag();
    geo.clear();
    geo.append( QPointF( -30, -30 ) );
    geo.append( QPointF( -30,  30 ) );
    geo.append( QPointF(  30,  30 ) );
    geo.append( QPointF(  30, -30 ) );
    editor.addGeo(geo);*/
    editor.showEditPanel();
    editor.show();

    new QShortcut( QKeySequence("Ctrl++"), &editor, SLOT( zoomIn() ) );
    new QShortcut( QKeySequence("Ctrl+-"), &editor, SLOT( zoomOut() ) );

    return app.exec();
}

/**************************************************************************************************/
